#include "Mipmap.h"

using namespace OBR;

template <typename DataType> Mipmap<DataType>::Mipmap()
{
}

template <typename DataType> Mipmap<DataType>::Mipmap(const Texture<DataType, 2> &src)
{
    this->createFromTexture(src);
}

template <typename DataType> Mipmap<DataType>::~Mipmap()
{
    delete[] this->levels;
}

template <typename DataType>
void __shrinkTexture(Texture<DataType, 2> &src, Texture<DataType, 2> &dst)
{
    // calc basic metadata & alloc memory
    dst.size[0] = src.size[0] >> 1;
    dst.size[1] = src.size[1] >> 1;
    const uint &n_row = dst.size[0];
    const uint &n_col = dst.size[1];
    uint dsize = sizeof(DataType) * n_row * n_col;
    dst.data = (DataType *)malloc(dsize);

    // calc data
    // TODO: use cuda to accelerate this
#pragma omp parallel for
    for (int x = 0; x < n_row; x++)
    {
        for (size_t y = 0; y < n_col; y++)
        {
            dst.data[x * n_row + y] = (src.at(x * 2, y * 2) + src.at(x * 2 + 1, y * 2) +
                                       src.at(x * 2, y * 2 + 1) + src.at(x * 2 + 1, y * 2 + 1)) /
                                      4.0f;
        }
    }

    // upload to device
    dst.upload();
}

template <typename DataType> void Mipmap<DataType>::uploadLevelData()
{
    DeviceTexture2D<DataType> *data = new DeviceTexture2D<DataType>[this->num_levels];
    for (size_t i = 0; i < this->num_levels; i++)
    {
        data[i] = this->levels[i].getDeviceTex2D();
    }
    const uint size = sizeof(DeviceTexture2D<DataType>) * (this->num_levels);
    this->d_mipmap_levels.resize(size);
    this->d_mipmap_levels.upload(data, size);
    delete[] data;
}

template <typename DataType> DeviceMipmap<DataType> Mipmap<DataType>::getDeviceMipmap()
{
    if (!this->initialized)
    {
        std::cerr << "calling getDeviceMipmap from uninitialized texture" << std::endl;
        throw std::runtime_error("calling getDeviceMipmap from uninitialized texture");
    }

    this->uploadLevelData();

    DeviceMipmap<DataType> d_mipmap;
    d_mipmap.levels =
        reinterpret_cast<DeviceTexture2D<DataType> *>(this->d_mipmap_levels.d_pointer());
    d_mipmap.num_levels = this->num_levels;
    d_mipmap.minstep = 1.0f / (1 << (this->num_levels - 1));
    return d_mipmap;
}

template <typename DataType>
void Mipmap<DataType>::createFromTexture(const Texture<DataType, 2> &src)
{
    /* ----------------------------- check pow2 ----------------------------- */
    assert((src.size[0] & (src.size[0] - 1)) == 0);
    assert((src.size[1] & (src.size[1] - 1)) == 0);

    /* ------------------------ calc & create levels ------------------------ */
    uint n_lods = log2f(min(src.size[0], src.size[1]));
    if (this->levels) // clear old records
    {
        delete[] this->levels;
    }
    this->levels = new Texture<DataType, 2>[n_lods + 1];
    this->num_levels = n_lods + 1;
    // level 0
    this->levels[0] = src;
    // following levels
    for (size_t l = 1; l < n_lods; l++)
    {
        __shrinkTexture(this->levels[l - 1], this->levels[l]);
    }

    this->initialized = true;
}

/* --------------------- instances of possible mipmaps ---------------------- */
template class Mipmap<Spectrum>;
template class Mipmap<float>;
/* -------------------------------------------------------------------------- */